package symbolrt.synthesis;

import java.util.List;
import java.util.Set;

import symbolrt.base.Action;
import symbolrt.base.Location;
import symbolrt.base.TIOSTS;
import symbolrt.base.TypedData;
import symbolrt.translation.parser.TranslationUnit;
import symbolrt.translation.parser.base.AbstractAction;
import symbolrt.translation.parser.base.AbstractExpression;
import symbolrt.translation.parser.base.AbstractTIOSTS;
import symbolrt.translation.parser.base.AbstractTransition;
import symbolrt.translation.parser.base.Element;
import symbolrt.translation.parser.base.StateElement;
import symbolrt.translation.parser.base.TypedElement;
import symbolrt.util.Constants;

/**
 * <code>Builder</code> Class. <br>
 * 
 * This class is responsible for TIOSTS models instantiations
 * 
 * @author Jeanderson Barros Candido ( <a
 *         href="mailto:jeandersonbc@gmail.com">jeandersonbc@gmail.com</a> )
 * 
 * @version 1.0 <br>
 *          (C) Copyright 2012 SYMBOL-RT Translator.
 */
class Builder {

	private TIOSTS processedSPEC = null;
	private TIOSTS processedTP = null;

	/**
	 * Initializes compilation process
	 * 
	 * @param spec
	 *            The {@link AbstractTIOSTS} representing the specification
	 * @param tp
	 *            The {@link AbstractTIOSTS} representing the test purpose
	 */
	public void init(AbstractTIOSTS spec, AbstractTIOSTS tp) {
		if (spec != null && tp != null) {
			this.processedSPEC = compileModel(true, spec);
			this.processedTP = compileModel(false, tp);
		}
	}

	private static final TIOSTS compileModel(boolean isSpecification,
			AbstractTIOSTS abstractModel) {
		TIOSTS model = new TIOSTS(abstractModel.getName());
		if (isSpecification) {
			setClocks(model);
			setVariables(model, abstractModel);
		}
		setParameters(model, abstractModel);
		setInternalActions(model, abstractModel);
		setInputActions(model, abstractModel);
		setOutputActions(model, abstractModel);
		setLocations(model, abstractModel);
		setTransitions(model, abstractModel);

		return model;
	}

	private static void setTransitions(TIOSTS model,
			AbstractTIOSTS abstractModel) {
		Set<AbstractTransition> transitions = abstractModel.getTransitions();
		for (AbstractTransition trans : transitions) {

			// Assignments
			String clockAssignments = transformAssignment(trans
					.getClockAssignments());
			String dataAssignments = transformAssignment(trans
					.getDataAssignments());

			// Locations
			String sourceLocation = trans.getSource();
			String targetLocation = trans.getTarget();

			// Guards
			String clockGuard = createGuard(trans.getClockGuard());
			String dataGuard = createGuard(trans.getDataGuard());
			if (model.getLocation(sourceLocation).isInitialLocation()) {
				model.setInitialCondition(dataGuard);
			}

			// Deadline
			String deadline = trans.getDeadline();

			// Action
			AbstractAction absAction = trans.getAction();
			Action action = null;
			if (absAction != null) {
				action = model.getAction(absAction.getType(),
						absAction.getName());

			} else
				action = model.getAction(Constants.ACTION_INTERNAL, "tau");

			if (deadline == null) {
				model.createTransition(sourceLocation, dataGuard, clockGuard,
						action, dataAssignments, clockAssignments,
						targetLocation);
			} else {
				model.createTransition(sourceLocation, dataGuard, clockGuard,
						action, dataAssignments, clockAssignments, deadline,
						targetLocation);
			}
		}
	}

	private static void setLocations(TIOSTS model, AbstractTIOSTS abstractModel) {
		Location location = null;
		List<Element> states = abstractModel.getStates();
		for (Element e : states) {
			StateElement e2 = (StateElement) e;
			if (e2.isInitial()) {
				location = new Location(e2.getName(), true);
				model.setInitialLocation(location);
			} else {
				location = new Location(e2.getName(), false);
			}
			model.addLocation(location);
		}
	}

	private static void setOutputActions(TIOSTS model,
			AbstractTIOSTS abstractModel) {
		Action action = null;
		for (AbstractAction act : abstractModel.getOutputActions()) {
			action = new Action(act.getName(), Constants.ACTION_OUTPUT);
			setActionParameters(action);
			model.addOutputAction(action);
		}
	}

	private static void setActionParameters(Action action) {
		List<Element> params = TranslationUnit.getParamsTable().get(
				action.getName());
		for (Element e : params) {
			action.addParameter(e.getName());
		}

	}

	private static void setInputActions(TIOSTS model,
			AbstractTIOSTS abstractModel) {
		Action action = null;
		for (AbstractAction act : abstractModel.getInputActions()) {
			action = new Action(act.getName(), Constants.ACTION_INPUT);
			setActionParameters(action);
			model.addInputAction(action);
		}
	}

	private static void setInternalActions(TIOSTS model,
			AbstractTIOSTS abstractModel) {
		Set<AbstractAction> internalActions = abstractModel
				.getInternalActions();
		if (internalActions == null || internalActions.size() == 0) {
			model.addInternalAction(new Action("tau", Constants.ACTION_INTERNAL));

		} else {
			Action action = null;
			for (AbstractAction act : internalActions) {
				action = new Action(act.getName(), Constants.ACTION_INTERNAL);
				if (act.getParameters() != null
						&& act.getParameters().size() > 0) {
					System.err
							.println("Warning: Internal action '"
									+ act.getName()
									+ "' has been declared with parameters. Deleting parameters...");
				}
				model.addInternalAction(action);
			}
		}
	}

	private static void setParameters(TIOSTS model, AbstractTIOSTS abstractModel) {
		TypedData actionParameter = null;
		for (TypedElement e : abstractModel.getParameters()) {
			actionParameter = new TypedData(e.getName(), e.getType());
			model.addActionParameter(actionParameter);
		}
	}

	private static void setClocks(TIOSTS model) {
		for (Element e : TranslationUnit.getClocks()) {
			model.addClock(e.getName());
		}
	}

	private static void setVariables(TIOSTS model, AbstractTIOSTS abstractModel) {
		TypedData variable = null;
		for (Element e : abstractModel.getVariables()) {
			variable = new TypedData(e.getName(), ((TypedElement) e).getType());
			model.addVariable(variable);
		}

	}

	private static String createGuard(AbstractExpression expr) {
		String guard;
		if (expr == null) {
			guard = Constants.GUARD_TRUE;
		} else {
			guard = transformExpression(expr);
		}
		return guard;
	}

	private static String transformExpression(AbstractExpression expr) {
		if (expr != null)
			return expr.toString();
		else
			return null;
	}

	private static String transformAssignment(
			Set<AbstractExpression> assignments) {
		if (assignments != null && !assignments.isEmpty()) {
			String str = "";
			for (AbstractExpression e : assignments) {
				str += " | " + e.toString();
			}
			return str.substring(2).trim();
		}
		return null;
	}

	/**
	 * @return the TIOSTS model representing the test purpose
	 */
	public TIOSTS getProcessedTP() {
		return this.processedTP;
	}

	/**
	 * @return the TIOSTS model representing the specification
	 */
	public TIOSTS getProcessedSpec() {
		return this.processedSPEC;
	}

}